// call 和 apply 在实际开发中的用途
/**
 * 1. 改变 this 指向
 */

var obj1 = {
    name: 'sven'
};

var obj2 = {
    name: 'anne'
};

window.name = 'window';

var getName = function () {
    console.log(this.name);
};

getName();            // window
getName.call(obj1);   // sven
getName.call(obj2);   // anne

// 在实际开发中，经常会遇到 this 指向被不经意改变的场景
// 用 call 来修正 func 函数内的 this，使其依然指向div1
document.getElementById('div1').onclick = function () {
    console.log(this.id); // div1, this => div1

    var func = function () {
        console.log(this.id);
    };
    // func(); // undefined, this => window
    func.call(this); // div1, this => div1
};

/**
 * 2. Function.prototype.bind
 * 
 * 大部分高级浏览器都实现了内置的 Function.prototype.bind，用来指定函数内的 this 指向，即使没有原生的 Function.prototype.bind，我们模拟一个也不是难事。
 */

//  简化版
Function.prototype.bind = function (context) {
    // context 就是想修正的对象
    // 保存原函数
    var self = this; 
    // 返回一个新的函数
    return function () {  
        // 执行新的函数的时候，会把之前传入的 context 当作新函数体内的 this
        return self.apply(context, arguments);
    }
};

var obj = {
    name: 'sven'
};

var func = function () {
    console.log(this.name);
}.bind(obj);

func(); // sven

// 复杂点
Function.prototype.bind = function () {
    // 保存原函数
    var self = this,  
    // 需要绑定的 this 上下文 
    // shift() 方法从数组中删除第一个元素，并返回该元素的值。此方法更改数组的长度。
        context = [].shift.call(arguments), 
    // 剩余的参数转成数组
        args = [].slice.call(arguments); 
    // 返回一个新的函数
    return function () {
        // 执行新的函数的时候，会把之前传入的 context 当作新函数体内的 this
        // 并且组合两次分别传入的参数，作为新函数的参数
        return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
    }
};

var obj = {
    name: 'sven'
};

var func = function (a, b, c, d) {
    console.log(this.name);
    console.log([a,b,c,d]);
}.bind(obj, 1, 2);

func(3,4); // sven, [1,2,3,4]